module Main where

{- All the required imports -}
import Data.Char                           (isDigit)
import Data.List                           (isPrefixOf, isInfixOf, isSuffixOf, intercalate)
import Data.Word                           (Word8)
import Network.BSD                         (getProtocolNumber)
import Network.Socket               hiding (send) -- We have our own send function
import qualified Network.Socket.ByteString (recv, send)
import Numeric                             (readDec)
import Functions
import Types

{-
    main - Started with the .exe
    
    Reads database files, config files
    Creates a socket and connects it to IRC server
    Creates a Bot and passes it to the handle function
-}
main :: IO ()
main = withSocketsDo $ do -- Enable socket support for Win32
    
    itemFile <- readFile "item_db.txt"
    let items = listToItem $ map (splitWith ',') $ fileFilter itemFile
    
    mobFile <- readFile "mob_db.txt"
    let mobs = listToMob $ map (splitWith ',') $ fileFilter mobFile
    
    file <- readFile "main.conf"
    let fileContent = fileFilter file
    
    let server = sConfig "server" fileContent -- Get server host
    let port   = sConfig "port" fileContent   -- Get server port
    let nick   = sConfig "nick" fileContent   -- Get bot nick
    time      <- getTimeSec                   -- Get starttime
    
    let conFilter = map sFilter $ patterns "[CONNECTING]" fileContent -- Load connecting filters
    let sysFilter = map sFilter $ patterns "[SYSTEM]" fileContent     -- Load system filters
    let patFilter = map sFilter $ patterns "[PATTERNS]" fileContent   -- Load patterns
    
    addrinfos <- getAddrInfo Nothing (Just server) (Just port)   -- Look up availiable hostnames and ports
    let serveraddr = head addrinfos                              -- The first one is the best one
    proto <- getProtocolNumber "tcp"                             -- Make sure it's TCP; todo: try default when done
    sock <- socket (addrFamily serveraddr) Stream proto          -- Create the socket
    setSocketOption sock KeepAlive 1                             -- Keep the socket alive
    connect sock (addrAddress serveraddr)                        -- Connect
    
    
    ircsend sock $ stringToWord $ "NICK " ++ nick                -- Send our nick
    ircsend sock $ stringToWord $ "USER " ++ nick ++ " 0 * :..." -- Send the user command
    
    botHandle (Bot sock Connecting nick False conFilter sysFilter patFilter time [] [] items mobs)
      where sConfig s  = clean . drop 1 . snd . span (/='=') . head . dropWhile (\x->take (length s) x /= s) -- Simple way of getting config options
            patterns t = takeWhile (\x->'[' /= head x) . drop 1 . dropWhile (/= t) -- Gets all patterns between the passed [String] and next [String]
            fileFilter = filter (\x->(take 2 x) /= "//") . filter (not . null) . lines -- Splits the file in to lines, removes the empty and commented lines

-- Used to build a list of Item from the item_db file
listToItem :: [[String]] -> [Item]
listToItem [] = []
listToItem (i:is) = do let s = intercalate "," (drop 19 i)
                       (Item (i!!0) (i!!1) (i!!2) (i!!3) (i!!4) (i!!5)  (i!!6)  (i!!7)  (i!!8)  (i!!9)  (i!!10)
                             (i!!11) (i!!12) (i!!13) (i!!14) (i!!15) (i!!16) (i!!17) (i!!18) (i!!19) (i!!20) (i!!21)) : listToItem is

-- Used to build a list of Mob from the mob_db file
listToMob :: [[String]] -> [Mob]
listToMob = map (\ m -> (Mob (m!!0) (m!!3) (m!!2) (m!!4) (m!!5) (m!!7) (m!!8) (listToPair $ filter ([]/=) $ take 18 $ drop 38 m) ((m!!56,m!!57))))
            
-- Turn a string in to a Filter, Filter is used to keep the structure intact
sFilter :: String -> Filter
sFilter p = do let (userlist,rest) = span (/=':') p                     -- Split userlist from filters
               let (activation,response) = span (/='=') $ drop 1 rest   -- Split the two filters
               let match   = s2ss $ clean activation                    -- remove spaces and turn to SubStr, the format used for filters
               let respond = s2ss $ clean $ drop 1 response
               case clean userlist of
                 ""   -> All match respond                              -- All users can use the command
                 user -> User (splitWith ',' userlist) match respond    -- Build userlist

-- Turns String in to SubStr, helpfunction to sFilter
s2ss :: String -> SubStr
s2ss []      = End                                          -- End of string
s2ss ('"':s) = S rest (s2ss $ drop (length rest+2) s)       -- It's a message between " "
                    where rest = pRest s '"'
s2ss (c:s)
  | isDigit c   = W (fst $ head $ readDec rest) recursive   -- It's a number -> [Word8]
  | otherwise   = F rest recursive                          -- It's a letter -> Function
    where rest = pRest (c:s) '.'
          recursive = s2ss $ drop (length rest) s

-- Helpfunction to s2ss, given a string and a char, returns the rest of the string before match
-- Ex: pRest "hello\"hi" '"' -> "hello"
pRest :: String -> Char -> String
pRest []    _             = []
pRest (c:s) l | c == l    = []
              | otherwise = c : (pRest s l)

{-
    botHandle
    
    Gets line from Socket
    Gets user, address, message
    Checks which filter to use and then pass it to mesIO
    Recursive call back to botHandle
-}
botHandle :: Bot -> IO ()
botHandle bot = do
    recieved <- Network.Socket.ByteString.recv (sock bot) 2048  -- Recieve msg from Socket
    let wrd = byteToWord recieved   -- ByteString -> [Word8]
    let msg = wordToString wrd      -- [Word8]    -> String
    -- appendFile "log.txt" ("Recieved: " ++ msg) -- Used for debugging
    putStrLn $ "Recieved: " ++ msg
    let msglist = words msg                         -- Splits the recieved message up in words 
    let (user',addr') = span (/='!') $ head msglist -- Gets user and address from first word
    let user = drop 1 user' -- Removes : from user
    let addr = case addr of -- Did we find an address?
                 "" -> ""
                 ot -> drop 1 addr'
    
    bot' <- case status bot of -- If we are connecting, it shouldn't check for patterns other then [CONNECTING]
              Connecting -> mesIO (botMes bot wrd) (connecting bot) user addr (unwords $ tail msglist)
              otherwise  -> if "PRIVMSG" `isInfixOf` msg then
                              mesIO (botMes bot wrd) (patterns bot) user addr (unwords $ tail msglist) -- Messages recieved in chat
							else
                              mesIO (botMes bot wrd) (system bot) user addr msg                        -- System messages
    botHandle bot' -- ^ Recursive call
    
    
    
{-
    ircsend
    
    Sends a string to the server
-}
ircsend :: Socket -> [Word8] -> IO ()
ircsend handle mes = case length mes of -- Only send if we have a message to send
                       0 -> putStr ""
                       o -> do size <- Network.Socket.ByteString.send handle $ wordToByte mes
                               putStr "" -- do has to end with IO (), send gave IO Int
                               -- appendFile "log.txt" ("...Sent [" ++ (show size) ++ "]: " ++ (wordToString mes) ++ "\n") -- Used for debugging

{-
    mesIO
    
    
-}
mesIO :: Bot -> [Filter] -> String -> String -> String -> IO Bot
mesIO bot filters user _ message = do
  time <- getTimeSec
  -- appendFile "log.txt" ("::: User: " ++ user ++ "\n" ++ "::: Addr: \n" ++ "::: Message: " ++ message ++ "\n" ++ "::: Filters: " ++ (show filters) ++ "\n") -- Used for debugging
  let (ans, bot') = filtersToWord filters bot user message time -- Turn the filters 
  mapM_ (ircsend (sock bot')) ans
  return bot'

filtersToWord :: [Filter] -> Bot -> String -> String -> Int -> ([[Word8]],Bot)
filtersToWord []     bot user message time = ([],bot)
filtersToWord (f:fs) bot user message time = (ans:ans', bot'')
                                     where (ans, bot')   = filterToWord  f  bot  user message time
                                           (ans', bot'') = filtersToWord fs bot' user message time

filterToWord :: Filter -> Bot -> String -> String -> Int -> ([Word8], Bot)
filterToWord (User ss match respond) bot user m t = if user `elem` ss then filterToWord (All match respond) bot user m t
                                                    else ([], bot)
filterToWord (All match respond) bot user message time = do
  let checkStr = fst $ subStrToString match bot user message time
  let check    = case head checkStr of
                   '!' -> checkStr `isPrefixOf` (drop 1 $ unwords $ drop 2 $ words message)
                   oth -> checkStr `isInfixOf` message
  if check then
    let (reponse, bot') = subStrToString respond bot user message time in (stringToWord reponse,bot')
    else ([],bot)
{-
    subStrToString
-}
subStrToString :: SubStr -> Bot -> String -> String -> Int -> (String, Bot)
subStrToString End     bot _ _ _ = ([], bot)
subStrToString (S s r) bot u m t = let (ans,bot') = subStrToString r bot u m t in
                                      (s ++ ans, bot')
subStrToString (W w r) bot u m t = let (ans,bot') = subStrToString r bot u m t in
                                      (wordToString [w] ++ ans, bot')
subStrToString (F f r) bot u m t = do let (res,bot') = callFunc f bot u m t
                                      let (ans,bot'') = subStrToString r bot' u m t 
                                      (res ++ ans, bot'')

-- Keeps track of functions to call, best way to organise since all the functions return values are different
-- All the functions called are in the "functions.hs" file
callFunc :: String -> Bot -> String -> String -> Int -> (String, Bot)
callFunc func bot user message time = case func of
  "ready" -> ("", ready bot)
  "locked" -> ("", locked bot)
  "sysquit" -> ("", botAddDis bot user time)
  "sysjoin" -> ("", botRemDis bot user)
  "sysjoin2" -> ("", botRemDis bot (drop 1 $ last $ words message))
  "send" -> (send message, bot)
  "channel" -> (channel message, bot)
  "pong" -> (pong bot, bot)
  "name" -> (nick bot, bot)
  "sender" -> (user, bot)
  "svn" -> (svn message, bot)
  "white" -> (white, bot)
  "red" -> (red, bot)
  "blue" -> (blue, bot)
  "lastonline" -> (lastOnline bot message time, bot)
  "item" -> (item bot message, bot)
  "il" -> (il bot message, bot)
  "sil" -> (sil bot message, bot)
  "mob" -> (mob bot message, bot)
  "ml" -> (ml bot message, bot)
  otherwise -> (leave, bot)

